package org.znxs.znagent_s.controller;

import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.znxs.znagent_s.agent.ZNManus;
import org.znxs.znagent_s.agent.tools.QueryUserTool;
import org.znxs.znagent_s.agent.tools.UserInputQueue;
import org.znxs.znagent_s.demo.ChatService;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;


@RestController
@RequestMapping("/ai")
@Slf4j
public class AIController {

//    @Resource
//    private ChatModel dashscopeChatModel;

//    @Resource
//    private ChatModel ollamaChatModel;

    @Resource
    private ChatService chatService;


    // 方式一 使用构造器注入
    private final ChatClient chatClient1;

    public AIController(ChatClient.Builder builder) {
        this.chatClient1 = builder
                // 设定默认的预设
                .defaultSystem("你是恋爱顾问江小白，我是{user}")
                // 自定义 顾问 实现一些额外功能
                .defaultAdvisors(
//                        new MessageChatMemoryAdvisor(chatMemory), // 对话记忆 advisor
//                        new QuestionAnswerAdvisor(vectorStore) // RAG 检索增强 advisor
                )
                .build();
    }

    // 方式二 采用建造者模式调用
//    ChatClient chatClient2 = ChatClient.builder(dashscopeChatModel)
//            .defaultSystem("你是恋爱顾问")
//            .build();


//    @GetMapping("/chat/dashscope")
//    public String dashscopeChatModelTest() {
//        AssistantMessage output = dashscopeChatModel.call(new Prompt("你好，我是左拿,你是谁？"))
//                .getResult()
//                .getOutput();
//        return output.getText();
//    }

//    @GetMapping("/chat/ollama")
//    public String ollamaChatModelTest() {
//        AssistantMessage output = ollamaChatModel.call(new Prompt("你好，我是左拿,你是谁？"))
//                .getResult()
//                .getOutput();
//        return output.getText();
//    }

    @GetMapping("/client/dashscope")
    public String dashscopeClientTest(@RequestParam String text) {
        // 使用建造者模式 构架实体类
//        ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
//                .defaultSystem("你是恋爱顾问")
//                .build();
        ChatClient chatClient3 = chatService.getChatClient();
        // 返回字符串
        String content = chatClient3.prompt().user("你好啊，" + text).call().content();
        // 返回聊天对象
//        ChatResponse chatResponse = chatClient3.prompt().user("Tell me a joke").call().chatResponse();
        // 返回指定对象形式
        record ActorFilms(String actor, List<String> movies) {
        }
//        ActorFilms actorFilms = chatClient3.prompt().user("Generate the filmography for a random actor.").call().entity(ActorFilms.class);
        //  返回集合
//        List<ActorFilms> actorFilmsList = chatClient3.prompt().user("为熊出没制作两部电影").call().entity(new ParameterizedTypeReference<List<ActorFilms>>() {
//        });
        // 对话时动态更改系统提示词的变量
//        String content3 = chatClient.prompt()
//                .system(sp -> sp.param("user", "左拿"))
//                .user("你好，我是谁？")
//                .call()
//                .content();

        // 对话时动态设定拦截器参数，比如指定对话记忆的 id 和长度
        String response = this.chatClient1.prompt()
                .advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
                        .param("chat_memory_response_size", 100))
                .user("你好，我是谁？")
                .call()
                .content();

        // 需要去了解Advisor的机制和原理 ✔️完成
        return response;
    }


    /**
     * 结构化数据处理 结构化处理用户返回对象的实例
     */
    public void constructHandle() {
        // 定义一个记录类
        record ActorsFilms(String actor, List<String> movies) {
        }

        // 【1】返回聊天对象 使用高级 ChatClient API 简单结构 直接转换
        ActorsFilms actorsFilms = chatClient1.prompt()
                .user("Generate 5 movies for Tom Hanks.")
                .call()
                .entity(ActorsFilms.class);
        // 【2】复杂结构 使用ParameterizedTypeReference 可以转换为自定义对象列表
        // 输出实体类的list结构
        List<ActorsFilms> actorsFilmsList = chatClient1.prompt()
                .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
                .call()
                .entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
                });
        // 将模型输出转换为包含数字列表的 Map
        Map<String, Object> actorsFilmsMap = chatClient1.prompt()
                .user(u -> u.text("Provide me a List of {subject}")
                        .param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
                .call()
                .entity(new ParameterizedTypeReference<Map<String, Object>>() {
                });

        // 【3】 转换成 将模型输出转换为字符串列表
        List<String> flavors = chatClient1.prompt()
                .user(u -> u.text("List five {subject}")
                        .param("subject", "ice cream flavors"))
                .call()
                .entity(new ListOutputConverter(new DefaultConversionService()));


    }


    @Resource
    private ToolCallback[] allTools;

    @Resource
    private ChatModel dashscopeChatModel;

    // 三种方式进行流式传输 SSE(Server Sent Events ) 流式传输

    /**
     * 方式一：使用Flux 进行传输  这种方式需要指定 MediaType类型为Stream
     *
     * @param query
     * @param chatId
     * @return
     */
    @GetMapping(value = "/agent/media/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatAgentFluxMediaStream(@RequestParam String query, @RequestParam String chatId) {
        ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
                .build();

        Flux<String> content = chatClient.prompt()
                .user(query)
                .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .stream()
                .content();
//        ZNManus znManus = new ZNManus(allTools, dashscopeChatModel);
//        Boolean think = znManus.think();
        return content;
    }

    /**
     * 方式二：使用ServerSentEvent作为返回封装类型
     *
     * @param query
     * @param chatId
     * @return
     */
    @GetMapping(value = "/agent/sse/")
    public Flux<ServerSentEvent<String>> chatAgentFluxServerSentEvent(@RequestParam String query, @RequestParam String chatId) {
        ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
                .build();
        Flux<String> content = chatClient.prompt()
                .user(query)
                .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .stream()
                .content();
        // 对流式消息进行处理
        Flux<ServerSentEvent<String>> res = content.map(chuck ->
                ServerSentEvent.<String>builder().data(chuck).build());
        return res;

    }

    /**
     * 方式三： 使用SseEmitter 但是编写的代码比较多
     *
     * @param query
     * @param chatId
     * @return
     */
    @GetMapping("/agent/sseEmitter")
    public SseEmitter chatAgentSseEmitter(@RequestParam String query, @RequestParam String chatId) {
        // 创建一个超时时间较长的SseEmitter
        SseEmitter sseEmitter = new SseEmitter(10000L);
        // 获取Flux 流直接并直接订阅
        ChatClient chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
                .build();
        Flux<String> content = chatClient.prompt()
                .user(query)
                .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .stream()
                .content();
        // 订阅
        content.subscribe(
                // 处理每条消息
                chunk -> {
                    try {
                        sseEmitter.send(chunk);
                    } catch (IOException e) {
                        // 发送错误，直接开始处理错误
                        sseEmitter.completeWithError(e);
                    }
                },
                // 处理错误
                sseEmitter::completeWithError,
                // 处理完成
                sseEmitter::complete
        );
        return sseEmitter;
    }


    /**
     * 左拿智能体
     *
     * @param query
     * @param request
     * @return
     */
    @GetMapping("/chat/ZnManus")
    public SseEmitter ZnManus(@RequestParam String query, HttpServletRequest request) {
        ZNManus znManus = new ZNManus(allTools, dashscopeChatModel);
        SseEmitter sseEmitter = znManus.runStream(query);
        return sseEmitter;
    }


    @Resource
    private UserInputQueue userInputQueue;

    /**
     * 询问用户后，用户输入接口后，前端进行调用该接口，把用户输入的内容传递到内容中
     *
     * @param input 用户输入内容
     * @return
     */
    @GetMapping("/user/input")
    public void userInput(@RequestParam("input") String input) {

        // 回答问题
        try {
            // 非空判断
            if (StrUtil.isBlank(input)) {
                userInputQueue.putResponse("用户输入为空");
            } else {
                userInputQueue.putResponse(input);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


}
